using System.Collections.Concurrent;
using System.Text.RegularExpressions;
using MultiAgentsClient.ChatDataStructures;
using MultiAgentsClient.Manager.DataModel;
using MultiAgentsClient.Service;
using MultiAgentsClient.Service.SignalRService;
using MultiAgentsClient.Service.WorkflowEngine;
using MultiAgentsClient.Service.WorkflowEngine.BuiltInNodes;
using MultiAgentsClient.Utils;
using MultiAgentsShared;
using OpenAI.ObjectModels.RequestModels;
using static MultiAgentsClient.Manager.LocalDataOperator;

namespace MultiAgentsClient.Manager;

public class MessageManager : ManagerBase
{
    private readonly SignalRChatService signalRChatService;
    private readonly WorkflowOperator workflowOperator;
    private readonly MessageWebviewOperator messageWebviewOperator;
    private readonly ToolSetCollection toolSetCollection;
    private readonly LocalDataOperator localDataOperator;

    private readonly SystemConfigDataModel systemConfigDataModel;
    private readonly UserDataModel userDataModel;
    private readonly WorkflowDataModel workflowDataModel;

    private readonly Regex antiInjectionRegex = new(@"^[0-9a-zA-Z\u4E00-\u9FA5\u3000-\u303F\uFF00-\uFFEF\\^$@._\-:+*?()[\]{} ]+$");
    private readonly Regex mentionRegex = new(@"@(\S+)");
    private readonly ConcurrentDictionary<int, MessageCollectionHelper> messageIdAndCollectionHelper = [];

    // recieve command from UI
    public record SendUserMessageCommand(string UserNewMsg, List<string> InputFiles);
    public record ResendUserMessageCommand(int MessageId, string Content);
    public record StopUserMessageCommand();
    public record LLMModelSelectionChangedCommand(string Model);
    public record RefreshMentionableListCommand(string Text);
    public record ToolCallUserConsentCommand(bool IsApprove, ChatMessageData ChatMessageData);

    // recieve command from signalR
    public record SendUserMsgResponseCommand(int RoomId, ChatMessageData ChatMessageData, string? MentionedAgent);
    public record OverrideMessageResponseCommand(int MessageId, string Content);
    public record SendToolResultsResponseCommand(int MsgId, int SenderId, int RoomId, string ToolCallId, string ToolCallResult, List<int>? HistoryMessagesIdList, DateTime Time);
    public record StreamSliceResponseCommand(int UserId, int RoomId, int MsgId, int SliceIndex, string Author, List<int>? HistoryMessagesIdList, string? StreamSlice, DateTime MsgTime, List<ToolCall>? ToolCalls);
    public record ReplyAssistantOptionsResponseCommand(int roomId, List<string> options);

    // send command to UI
    public record SendUserMessageFailCommand();
    public record SendUserMessageCompleteCommand(ChatMessageData ChatMessageData, string? MentionedAgent);
    public record AllowSendMessageCommand(bool IsAllow);
    public record SendToolResultCompleteCommand(ChatMessageData ToolCallResult);
    public record ChatContentUpdateCommand(ChatMessageData ChatMsg);
    public record ReplyAssistantCloseCommand();
    public record ReplyAssistantOptionsCommand(List<string> Options);
    public record MentionableRefreshedCompleteCommand(List<AgentDisp> AgentsDisp, List<WorkflowDisp> MentionableWorkflowDisp);
    public record ToolCallRequstCommand(ChatMessageData ChatMessage);
    public record LLMModelSelectionChangedComleteCommand(string Model);

    public MessageManager(
        SignalRChatService signalRChatService,
        ToolSetCollection toolSetCollection,
        SystemConfigDataModel systemConfigDataModel,
        UserDataModel userDataModel,
        WorkflowDataModel workflowDataModel,
        WorkflowOperator workflowOperator,
        MessageWebviewOperator messageWebviewOperator,
        LocalDataOperator localDataOperator)
    {
        this.systemConfigDataModel = systemConfigDataModel;
        this.signalRChatService = signalRChatService;
        this.toolSetCollection = toolSetCollection;
        this.workflowDataModel = workflowDataModel;
        this.workflowOperator = workflowOperator;
        this.userDataModel = userDataModel;
        this.messageWebviewOperator = messageWebviewOperator;
        this.localDataOperator = localDataOperator;

        eventBus.Subscribe<NodeOutputRequest>(cmd => { Task.Run(() => NodeOutputRequestHandler(cmd)); });
        eventBus.Subscribe<ToolCallUserConsentCommand>(cmd => { Task.Run(() => ToolCallUserConsentCommandHandler(cmd)); });
        eventBus.Subscribe<ToolCallServiceResponse>(cmd => { Task.Run(() => ToolCallServiceRequestHandler(cmd)); });
        eventBus.Subscribe<SendUserMessageCommand>(cmd => { Task.Run(() => SendUserMessageCommandHandler(cmd)); });
        eventBus.Subscribe<ResendUserMessageCommand>(cmd => { Task.Run(() => ResendUserMessageCommandHandler(cmd)); });
        eventBus.Subscribe<StopUserMessageCommand>(cmd => { Task.Run(() => StopUserMsgUserCommandHandler(cmd)); });
        eventBus.Subscribe<LLMModelSelectionChangedCommand>(cmd => { Task.Run(() => LLMModelSelectionChangedCommandHandler(cmd)); });
        eventBus.Subscribe<RefreshMentionableListCommand>(cmd => { Task.Run(() => RefreshMentionableListCommandHandler(cmd)); });

        eventBus.Subscribe<SendUserMsgResponseCommand>(cmd => { Task.Run(() => SendUserMsgResponseCommandHandler(cmd)); });
        eventBus.Subscribe<OverrideMessageResponseCommand>(cmd => { Task.Run(() => SaveEditMessageResponseCommandHandler(cmd)); });
        eventBus.Subscribe<SendToolResultsResponseCommand>(cmd => { Task.Run(() => SendToolResultsResponseCommandHandler(cmd)); });
        eventBus.Subscribe<StreamSliceResponseCommand>(cmd => { Task.Run(() => StreamSliceResponseCommandHandler(cmd)); });
        eventBus.Subscribe<ReplyAssistantOptionsResponseCommand>(cmd => { Task.Run(() => ReplyAssistantOptionsResponseCommandHandler(cmd)); });
    }

    #region request
    private async Task NodeOutputRequestHandler(NodeOutputRequest cmd)
    {
        eventBus.Publish(new NodeOutputResponse(cmd.NodeId));
        await signalRChatService.SendSignalRRequest(SignalRChatService.ApiRequestType.SendNodeMessageApiRequest.ToString(), new Dictionary<string, object>()
            {
                { "sessionId", systemConfigDataModel.sessionId },
                { "userId", userDataModel.Id },
                { "userName", userDataModel.Name },
                { "roomId", cmd.chatRoomId },
                { "message", cmd.outputContent },
                { "workflowName", cmd.workflowName },
            });
    }

    private void ToolCallUserConsentCommandHandler(ToolCallUserConsentCommand cmd)
    {
        ChatProject? chatProject = userDataModel.chatProjects.FirstOrDefault(prj => prj.Id == userDataModel.selectedPrjId);
        ChatRoom? chatRoom = chatProject?.ChatRoomList.FirstOrDefault(chatRoom => chatRoom.Id == userDataModel.selectedChatRoomId);
        if (cmd.IsApprove)
        {
            if (cmd.ChatMessageData.TryCompleteToolsCall() == false)
            {
                return;
            }

            IList<ToolCall>? toolCalls = cmd.ChatMessageData.toolCalls;
            if (toolCalls == null || toolCalls.Count == 0)
            {
                throw Debug.ShouldNotHappen();
            }

            string? lastUserMessage = chatRoom?.defaultAgent.ChatListContainer.ChatList[1].content;

            Regex regex = new(@"@(\S+)");
            Match match = regex.Match(lastUserMessage ?? "");
            string agentTypeName = match.Success ? match.Groups[1].Value : "DefaultModel";

            //ToolCallId, ToolCallResult
            eventBus.Publish(new ToolCallRequest(agentTypeName, toolCalls, cmd.ChatMessageData.Id));
        }
        else
        {
            eventBus.Publish(new AllowSendMessageCommand(true));
        }
    }

    private async Task ToolCallServiceRequestHandler(ToolCallServiceResponse response)
    {
        await signalRChatService.SendSignalRRequest(SignalRChatService.ApiRequestType.SendToolResultApiRequest.ToString(), new Dictionary<string, object>()
            {
                { "sessionId", systemConfigDataModel.sessionId },
                { "userId", userDataModel.Id },
                { "userName", userDataModel.Name },
                { "toolCallMessageId", response.ToolCallMessageId },
                { "model", systemConfigDataModel.selectingModel },
                { "osSystemMsg", OSInfoHelper.GetInfo() },
                { "toolCallResults", response.ToolCallResults },
            });
    }

    private async Task ResendUserMessageCommandHandler(ResendUserMessageCommand cmd)
    {
        await signalRChatService.SendSignalRRequest(SignalRChatService.ApiRequestType.ResendMessageApiRequest.ToString(), new Dictionary<string, object>()
            {
                { "sessionId", systemConfigDataModel.sessionId },
                { "userId", userDataModel.Id },
                { "messageId", cmd.MessageId },
                { "content", cmd.Content },
                { "model", systemConfigDataModel.selectingModel },
                { "osSystemMsg", OSInfoHelper.GetInfo() },
                { "toolSetJson", toolSetCollection.GetToolSetsJson() },
            });
        eventBus.Publish(new AllowSendMessageCommand(false));
    }

    private async Task SendUserMessageCommandHandler(SendUserMessageCommand sendMessageCmd)
    {
        ChatProject? chatProject = userDataModel.chatProjects.FirstOrDefault(prj => prj.Id == userDataModel.selectedPrjId);
        ChatRoom? chatRoom = chatProject?.ChatRoomList.FirstOrDefault(chatRoom => chatRoom.Id == userDataModel.selectedChatRoomId);

        if (antiInjectionRegex.IsMatch(userDataModel.Name) == false)
        {
            eventBus.Publish(new SendUserMessageFailCommand());
            return;
        }

        Match mentionMatch = mentionRegex.Match(sendMessageCmd.UserNewMsg);
        if (mentionMatch.Success)
        {
            var mentionedName = mentionMatch.Groups[1].Value;
            var mentionedWorkflow = workflowDataModel.GetWorkflow(mentionedName);
            if (mentionedWorkflow != null)
            {
                // Remove the matched part from the original message
                var beforeMatch = sendMessageCmd.UserNewMsg[..mentionMatch.Index];
                var afterMatch = sendMessageCmd.UserNewMsg[(mentionMatch.Index + mentionMatch.Length + 1)..];

                // Combine the parts before and after the match to get the rest of the message
                var userMsg = beforeMatch + afterMatch;

                await signalRChatService.SendSignalRRequest(SignalRChatService.ApiRequestType.SendUserMessageWithoutLLMResponseApiRequest.ToString(), new Dictionary<string, object>()
                    {
                        { "sessionId", systemConfigDataModel.sessionId },
                        { "userId", userDataModel.Id },
                        { "userName", userDataModel.Name },
                        { "roomId", userDataModel.selectedChatRoomId },
                        { "message", sendMessageCmd.UserNewMsg },
                    });

                workflowOperator.ExecuteManualWorkflow(mentionedWorkflow.workflowId, userMsg, userDataModel.GetCurrentRoomHistory(), userDataModel.selectedChatRoomId);

                eventBus.Publish(new AllowSendMessageCommand(true));
                return;
            }
        }

        var clientToolSetConfig = toolSetCollection.GetToolSetsJson();
        if (string.IsNullOrEmpty(systemConfigDataModel.selectingModel))
        {
            return;
        }

        string userMessage = sendMessageCmd.UserNewMsg;
        Dictionary<string, string> textFiles = [];
        Dictionary<string, string> images = [];
        foreach (string filePath in sendMessageCmd.InputFiles)
        {
            if (string.IsNullOrEmpty(filePath))
            {
                throw Debug.ShouldNotHappen();
            }

            string extension = Path.GetExtension(filePath).ToLowerInvariant();
            string[] readableImageExtensions = [".png", ".jpg", ".jpeg", ".webp"];
            string[] readableTextExtensions = [
                    ".txt", ".csv", ".json", ".xml", ".htm", ".md", ".ini", ".yaml", ".yml", ".log", ".rtf",
                    ".c", ".cpp", ".cc", ".cxx", ".cs", ".java", ".py", ".js", ".ts", ".html", ".css", ".php", ".rb", ".go",
                    ".swift", ".kt", ".m", ".sh", ".bat", ".pl", ".vb", ".sql", ".r", ".lua"
                ];

            if (readableImageExtensions.Contains(extension))
            {
                byte[] imageBytes = File.ReadAllBytes(filePath);
                string base64Image = Convert.ToBase64String(imageBytes);
                images.Add(Path.GetFileName(filePath), base64Image);
            }
            else if (readableTextExtensions.Contains(extension))
            {
                string fileContent = FileOperationToolProvider.LoadFile(filePath);

                textFiles.Add(Path.GetFileName(filePath), fileContent);
            }
        }

        await signalRChatService.SendSignalRRequest(SignalRChatService.ApiRequestType.SendUserMsgApiRequest.ToString(), new Dictionary<string, object>()
        {
            { "sessionId", systemConfigDataModel.sessionId },
            { "userId", userDataModel.Id },
            { "userName", userDataModel.Name },
            { "roomId", userDataModel.selectedChatRoomId },
            { "osSystemMsg", OSInfoHelper.GetInfo() },
            { "message", sendMessageCmd.UserNewMsg },
            { "textFiles", textFiles },
            { "images", images },
            { "model", systemConfigDataModel.selectingModel },
            { "toolSetJson", clientToolSetConfig },
        });
        eventBus.Publish(new AllowSendMessageCommand(false));
    }

    private async Task StopUserMsgUserCommandHandler(StopUserMessageCommand _)
    {
        await signalRChatService.SendSignalRRequest(SignalRChatService.ApiRequestType.StopUserMsgApiRequest.ToString(), new Dictionary<string, object>()
            {
                { "userId", userDataModel.Id },
                { "sessionId", systemConfigDataModel.sessionId },
            });
    }

    private void LLMModelSelectionChangedCommandHandler(LLMModelSelectionChangedCommand cmd)
    {
        systemConfigDataModel.selectingModel = cmd.Model;
        localDataOperator.UpdateUserSettingsData(BasicUserSettingsData.RoomModel, cmd.Model);

        eventBus.Publish(new LLMModelSelectionChangedComleteCommand(cmd.Model));
    }

    private void RefreshMentionableListCommandHandler(RefreshMentionableListCommand agentRefreshCmd)
    {
        string text = agentRefreshCmd.Text;

        int index = text.LastIndexOf('@');
        if (index == -1)
        {
            eventBus.Publish(new MentionableRefreshedCompleteCommand([], []));
            return;
        }

        string substr = text[(index + 1)..];

        if (substr.Contains(' '))
        {
            eventBus.Publish(new MentionableRefreshedCompleteCommand([], []));
            return;
        }

        List<AgentDisp> agentListDisp = [];
        List<WorkflowDisp> workflowListDisp = [];

        bool firstFound = false;
        foreach (AgentTemplate agent in userDataModel.customAgents)
        {
            if (agent.Name.StartsWith(substr, StringComparison.OrdinalIgnoreCase))
            {
                if (!firstFound)
                {
                    agentListDisp.Add(new AgentDisp(agent.Id, agent.Name, true));
                    firstFound = true;
                }
                else
                {
                    agentListDisp.Add(new AgentDisp(agent.Id, agent.Name, false));
                }
            }
        }

        foreach (BehaviorTreeConfig workflow in workflowDataModel.GetManualWorkflowConfigs().Values)
        {
            if (workflow.name.StartsWith(substr, StringComparison.OrdinalIgnoreCase))
            {
                if (!firstFound)
                {
                    workflowListDisp.Add(new WorkflowDisp(workflow.workflowId, workflow.name, true));
                    firstFound = true;
                }
                else
                {
                    workflowListDisp.Add(new WorkflowDisp(workflow.workflowId, workflow.name, false));
                }
            }
        }

        eventBus.Publish(new MentionableRefreshedCompleteCommand(agentListDisp, workflowListDisp));
    }
    #endregion

    #region SignalR Responses
    private void ReplyAssistantOptionsResponseCommandHandler(ReplyAssistantOptionsResponseCommand cmd)
    {
        //this is in case when the signal Reply user already switched to a new room
        if (cmd.roomId == userDataModel.selectedChatRoomId)
        {
            eventBus.Publish(new ReplyAssistantOptionsCommand(cmd.options));
        }
    }

    private void SendToolResultsResponseCommandHandler(SendToolResultsResponseCommand cmd)
    {
        ChatRoom? selectedChatRoom = userDataModel.GetChatRoom(userDataModel.selectedChatRoomId);

        if (selectedChatRoom == null)
        {
            return;
        }

        ChatMessageData chatMessageData = selectedChatRoom.AddToolResultMessage(cmd.MsgId, userDataModel.Id, cmd.ToolCallId, cmd.ToolCallResult, cmd.HistoryMessagesIdList, 0, cmd.Time);

        eventBus.Publish(new SendToolResultCompleteCommand(chatMessageData));
        messageWebviewOperator.AddMessage(chatMessageData.ToDisp());
    }

    private void SendUserMsgResponseCommandHandler(SendUserMsgResponseCommand cmd)
    {
        ChatRoom? selectedChatRoom = userDataModel.GetChatRoom(userDataModel.selectedChatRoomId);

        if (selectedChatRoom == null)
        {
            return;
        }

        selectedChatRoom.AddChatMessageData(cmd.ChatMessageData);
        eventBus.Publish(new SendUserMessageCompleteCommand(cmd.ChatMessageData, cmd.MentionedAgent));
        eventBus.Publish(new ReplyAssistantCloseCommand());
        messageWebviewOperator.AddMessage(cmd.ChatMessageData.ToDisp());
    }


    private void SaveEditMessageResponseCommandHandler(OverrideMessageResponseCommand cmd)
    {
        userDataModel.UpdateMessageContent(cmd.MessageId, cmd.Content);
    }

    private async Task StreamSliceResponseCommandHandler(StreamSliceResponseCommand cmd)
    {
        ChatRoom? room = userDataModel.GetChatRoom(cmd.RoomId);
        if (room == null)
        {
            return;
        }

        MessageCollectionHelper messageCollectionHelper = messageIdAndCollectionHelper.GetOrAdd(cmd.MsgId, new MessageCollectionHelper());

        ChatMessageData? chatMessage = await messageCollectionHelper.UpdateRespondedMsgFromStream(0, cmd.MsgId, cmd.SliceIndex, cmd.Author, cmd.StreamSlice, cmd.MsgTime, cmd.HistoryMessagesIdList, cmd.ToolCalls);

        if (chatMessage == null)
        {
            return;
        }

        if (chatMessage.msgStreamFinished)
        {
            room.defaultAgent.ChatListContainer.ChatList.Insert(0, chatMessage);

            if (chatMessage.toolCalls != null && chatMessage.toolCalls.Count > 0)
            {
                bool requireApprove = toolSetCollection.IfToolCallsRequireApproval(chatMessage.toolCalls);
                chatMessage.SetToolCalls(chatMessage.toolCalls, requireApprove);

                if (requireApprove)
                {
                    eventBus.Publish(new ToolCallRequstCommand(chatMessage));
                }
                else
                {
                    eventBus.Publish(new ToolCallUserConsentCommand(true, chatMessage));
                }
            }

            messageCollectionHelper.Dispose();
            messageIdAndCollectionHelper.TryRemove(cmd.MsgId, out _);
        }
        
        //TODO: Change 'Assistant' to agentName, and 0 to SenderId

        ChatMessageDisp chatMessageData = chatMessage.ToDisp();

        if (userDataModel.selectedChatRoomId != cmd.RoomId)
        {
            return;
        }
        else if (messageWebviewOperator.MaxMessagesId < chatMessageData.Id)
        {
            messageWebviewOperator.AddMessage(chatMessageData);
        }
        else if (userDataModel.selectedChatRoomId == cmd.RoomId && chatMessageData.MsgStreamFinished == false || string.IsNullOrEmpty(chatMessageData.Content) == false)
        {
            await messageWebviewOperator.UpdateMessageAsync(chatMessageData);
        }

        if(chatMessageData.MsgStreamFinished == true && chatMessageData.ToolCalls == null)
        {
            eventBus.Publish(new AllowSendMessageCommand(true));
        }
    }
    #endregion
}
